package com.demo.common.model;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.sql.DataSource;

import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.MetaBuilder;
import com.jfinal.plugin.activerecord.generator.TableMeta;

public class MetaBuilderEx extends MetaBuilder {

	public MetaBuilderEx(DataSource dataSource) {
		super(dataSource);
	}
	
	
	protected Map<String, TableMeta> tables = new LinkedHashMap<String, TableMeta>();
 

	public List<TableMeta> build() {
		List<TableMeta> ret = super.build();
		
		for (TableMeta tm : ret) {
			tables.put(tm.name, tm);
			changeColumnMetaToEx(tm);
		}
		for (TableMeta tableMeta : ret) {
			buildFKColumnMetas(tableMeta);
		}
		return ret;
	}

	protected void changeColumnMetaToEx(TableMeta tm) {
		for (int i = 0; i < tm.columnMetas.size(); i++) {
			ColumnMeta cm = tm.columnMetas.get(i);
			ColumnMetaEx cmex = new ColumnMetaEx();
			cmex.attrName = cm.attrName;
			cmex.defaultValue = cm.defaultValue;
			cmex.isNullable = cm.isNullable;
			cmex.isPrimaryKey = cm.isPrimaryKey;
			cmex.javaType = cm.javaType;
			cmex.name = cm.name;
			cmex.remarks = cm.remarks;
			cmex.type = cm.type;
			tm.columnMetas.set(i, cmex);
		}
	}

	protected void buildFKColumnMetas(TableMeta tableMeta) {
		FKEngine fkEngine = FKEngine.engine;
		for (ColumnMeta srccm : tableMeta.columnMetas) {
			ColumnMetaEx cm = (ColumnMetaEx)srccm;
			if (fkEngine.isMasterFK(tableMeta.name, cm.name)) {
				cm.slaveFields = new ArrayList<ColumnMetaEx>();
				cm.slaveTables = new ArrayList<TableMeta>();
				cm.slaveFK = new ArrayList<FK>();
				cm.isMasterFK = true;
				Map<String, FK> fks = fkEngine.getSlaveLink(tableMeta.name, cm.name);
				for (Entry<String, FK> entry : fks.entrySet()) {
					String slaveTableAndField = entry.getKey();
					String[] tmp = slaveTableAndField.split("[.]");
					String slaveTableName = tmp[0];
					String slaveField = tmp[1];
					FK fk = entry.getValue(); 
					TableMeta slaveTable = tables.get(slaveTableName);
					cm.slaveTables.add(slaveTable);
					for (ColumnMeta scm : slaveTable.columnMetas) {
						ColumnMetaEx slavecm = (ColumnMetaEx) scm;
						if (slavecm.name.equals(slaveField)) {
							cm.slaveFields.add(slavecm);
							cm.slaveFK.add(fk);
							break;
						}
					}
				}
				if(cm.slaveFields.size() != cm.slaveTables.size()){
					throw new RuntimeException("从表  和 从表字段  未定义正确");
				} 
			} else if (fkEngine.isSlaveFK(tableMeta.name, cm.name)) {
				// 作为外键id的使用者
				cm.isSlaveFK = true;
				FK fk = fkEngine.getMasterLink(tableMeta.name, cm.name);
				cm.masterFK = fk;
				cm.masterTable = tables.get(fk.masterTableName);
				for (ColumnMeta mcm : cm.masterTable.columnMetas) {
					ColumnMetaEx mastercm = (ColumnMetaEx) mcm;
					if (mastercm.name.equals(fk.masterField)) {
						cm.masterField = mastercm;
						break;
					}
				}
			}
		}
	}

 
	/**
	 * 文档参考：
	 * http://dev.mysql.com/doc/connector-j/en/connector-j-reference-type-conversions.html
	 * 
	 * JDBC 与时间有关类型转换规则，mysql 类型到 java 类型如下对应关系：
	 * DATE				java.sql.Date
	 * DATETIME			java.sql.Timestamp
	 * TIMESTAMP[(M)]	java.sql.Timestamp
	 * TIME				java.sql.Time
	 * 
	 * 对数据库的 DATE、DATETIME、TIMESTAMP、TIME 四种类型注入 new java.util.Date()对象保存到库以后可以达到“秒精度”
	 * 为了便捷性，getter、setter 方法中对上述四种字段类型采用 java.util.Date，可通过定制 TypeMapping 改变此映射规则
	 */
	protected void buildColumnMetas(TableMeta tableMeta) throws SQLException {
		String sql = dialect.forTableBuilderDoBuild(tableMeta.name);
		Statement stm = conn.createStatement();
		ResultSet rs = stm.executeQuery(sql);
		ResultSetMetaData rsmd = rs.getMetaData();
		
		for (int i=1; i<=rsmd.getColumnCount(); i++) {
			ColumnMeta cm = new ColumnMetaEx(); //只改变了类型
			cm.name = rsmd.getColumnName(i);
			
			String typeStr = null;
			if (dialect.isKeepByteAndShort()) {
				int type = rsmd.getColumnType(i);
				if (type == Types.TINYINT) {
					typeStr = "java.lang.Byte";
				} else if (type == Types.SMALLINT) {
					typeStr = "java.lang.Short";
				}
			}
			
			if (typeStr == null) {
				String colClassName = rsmd.getColumnClassName(i);
				typeStr = typeMapping.getType(colClassName);
			}
			
			if (typeStr == null) {
				int type = rsmd.getColumnType(i);
				if (type == Types.BINARY || type == Types.VARBINARY || type == Types.LONGVARBINARY || type == Types.BLOB) {
					typeStr = "byte[]";
				} else if (type == Types.CLOB || type == Types.NCLOB) {
					typeStr = "java.lang.String";
				}
				// 支持 oracle 的 TIMESTAMP、DATE 字段类型，其中 Types.DATE 值并不会出现
				// 保留对 Types.DATE 的判断，一是为了逻辑上的正确性、完备性，二是其它类型的数据库可能用得着
				else if (type == Types.TIMESTAMP || type == Types.DATE) {
					typeStr = "java.util.Date";
				}
				// 支持 PostgreSql 的 jsonb json
				else if (type == Types.OTHER) {
					typeStr = "java.lang.Object";
				} else {
					typeStr = "java.lang.String";
				}
			}
			cm.javaType = typeStr;
			
			// 构造字段对应的属性名 attrName
			cm.attrName = buildAttrName(cm.name);
			
			tableMeta.columnMetas.add(cm);
		}
		
		rs.close();
		stm.close();
	}

}
